﻿using System;
using System.ComponentModel;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Drawing;
using System.Text;
using System.Configuration;

using BizElements.BusinessLayer;

namespace BizElements.Web
{
    /// <summary>
    /// <see cref="DataGrid"/> template which displays and edits the value of a bound field in a <see cref="BoundLookup"/> control.
    /// </summary>
    public class BoundLookupTemplate : BoundTemplateBase, ISearchPageClient
    {
        #region Configuration.

        /// <summary>
        /// Gets default value for <see cref="AutoPopupOnChange"/> property.
        /// </summary>
        /// <value>Boolean value assigned to AutoPopupOnChange property. Default is <b>false</b>.</value>
        /// <remarks>Default configuration is configured in the <b>appSettings</b> section of config file. Example:
        /// <code>
        /// <add key="BizElements.Web.BoundLookupTemplate.DefaultAutoPopupOnChange" value="true"/>
        /// </code>
        /// </remarks>
        public static readonly bool DefaultAutoPopupOnChange = false;

        static BoundLookupTemplate()
        {
            string cfgDefaultAutoPopupOnChange = ConfigurationManager.AppSettings["BizElements.Web.BoundLookupTemplate.DefaultAutoPopupOnChange"];
            if (!string.IsNullOrEmpty(cfgDefaultAutoPopupOnChange))
            {
                cfgDefaultAutoPopupOnChange = cfgDefaultAutoPopupOnChange.ToLower();
                DefaultAutoPopupOnChange = (cfgDefaultAutoPopupOnChange == "true");
            }
        }

        #endregion

        #region FieldPermissions.

        string setValuesJScriptName = "SetValues";
        string hideLookupJScriptName = "HideLookup";
        string popupUrl;
        int[] additionalDisplayColumns;
        string clientIdQueryKey;
        bool prefixAllScriptsWithClientId;

        bool autoPopupOnChange;
        string clearImageUrl;
        string lookupImageUrl;
        Unit popupHeight;
        int popupLeftPx;
        int popupTopPx;
        Unit popupWidth;
        string textQueryKey;
        string toolTip;
        bool readOnly;
        int displayFieldsCount = 1;
        string displayFieldsDelimiter = " ";

        #endregion

        #region Constructors.

        /// <summary>
        /// Initializes a new instance of the <b>BoundLookupTemplate</b> class.
        /// </summary>
        public BoundLookupTemplate()
        {
            this.AutoPopupOnChange = DefaultAutoPopupOnChange;
        }

        /// <summary>
        /// Initializes a new instance of the <b>BoundLookupTemplate</b> class.
        /// </summary>
        /// <param name="useJsCommandParentReplacementStringContextVariable">If set to <b>true</b> all generated 
        /// scripts will be prefixed with a unique client-side control ID. ClientID is passed to popup form 
        /// by URL using the <see cref="JsCommand.ContextVariable_ParentReplacementString"/> query key.</param>
        public BoundLookupTemplate(bool useJsCommandParentReplacementStringContextVariable)
        {
            this.AutoPopupOnChange = DefaultAutoPopupOnChange;
            if (useJsCommandParentReplacementStringContextVariable)
            {
                this.prefixAllScriptsWithClientId = true;
                this.clientIdQueryKey = JsCommand.ContextVariable_ParentReplacementString;
            }
        }

        /// <summary>
        /// Initializes a new instance of the <b>BoundLookupTemplate</b> class.
        /// </summary>
        /// <param name="field"><see cref="IBindableField"/> that contains a reference and meta-data for a bound business class property.</param>
        /// <param name="formatter">The <see cref="IMessageFormatter"/> object used to format bound values.</param>
        public BoundLookupTemplate(IBindableField field, IMessageFormatter formatter)
            : base(field, formatter)
        {
            this.AutoPopupOnChange = DefaultAutoPopupOnChange;
        }

        /// <summary>
        /// Initializes a new instance of the <b>BoundLookupTemplate</b> class.
        /// </summary>
        /// <param name="field"><see cref="IBindableField"/> that contains a reference and meta-data for a bound business class property.</param>
        /// <param name="formatter">The <see cref="IMessageFormatter"/> object used to format bound values.</param>
        /// <param name="popupUrl">The URL of the popup page which performs the lookup action.</param>
        public BoundLookupTemplate(IBindableField field, IMessageFormatter formatter, string popupUrl)
            : base(field, formatter)
        {
            this.AutoPopupOnChange = DefaultAutoPopupOnChange;
            this.popupUrl = popupUrl;
        }

        /// <summary>
        /// Initializes a new instance of the <b>BoundLookupTemplate</b> class.
        /// </summary>
        /// <param name="field"><see cref="IBindableField"/> that contains a reference and meta-data for a bound business class property.</param>
        /// <param name="formatter">The <see cref="IMessageFormatter"/> object used to format bound values.</param>
        /// <param name="popupUrl">The URL of the popup page which performs the lookup action.</param>
        /// <param name="useJsCommandParentReplacementStringContextVariable">If set to <b>true</b> all generated 
        /// scripts will be prefixed with a unique client-side control ID. ClientID is passed to popup form 
        /// by URL using the <see cref="JsCommand.ContextVariable_ParentReplacementString"/> query key.</param>
        public BoundLookupTemplate(IBindableField field, IMessageFormatter formatter, string popupUrl,
            bool useJsCommandParentReplacementStringContextVariable)
            : base(field, formatter)
        {
            this.AutoPopupOnChange = DefaultAutoPopupOnChange;
            this.popupUrl = popupUrl;
            if (useJsCommandParentReplacementStringContextVariable)
            {
                this.prefixAllScriptsWithClientId = true;
                this.clientIdQueryKey = JsCommand.ContextVariable_ParentReplacementString;
            }
        }

        #endregion

        #region Properties.

        /// <summary>
        /// Gets or sets the name of the client-side script which is used to set the values selected by the popup
        /// window into <b>BoundLookup</b> control.
        /// </summary>
        /// <remarks><para>The generated script requires at least two parameters. The first parameter sets the 
        /// hidden value of <see cref="BoundLookup.SelectedValue"/> property, and the other sets the values which is displayed 
        /// to the user (see <see cref="BizElements.Web.BoundLookup.Text"/>).</para>
        /// <para>Number of parameters is greater than two if <see cref="AdditionalDisplayColumns"/> contains
        /// indices of other grid columns. One parameter per additional display control is generated.</para>
        /// </remarks>
        public virtual string SetValuesJScriptName
        {
            get { return this.setValuesJScriptName; }
            set { this.setValuesJScriptName = value; }
        }

        /// <summary>
        /// Gets or sets the name of the client-side script which hides the popup window.
        /// </summary>
        /// <remarks>Hide function requires no parameters.</remarks>
        public virtual string HideLookupJScriptName
        {
            get { return this.hideLookupJScriptName; }
            set { this.hideLookupJScriptName = value; }
        }

        /// <summary>
        /// Gets or sets the URL of the popup page which performs the lookup action.
        /// </summary>
        public virtual string PopupUrl
        {
            get { return this.popupUrl; }
            set { this.popupUrl = value; }
        }

        /// <summary>
        /// Gets or sets indices of columns of a parent <see cref="DataGrid"/> control which display additional fields.
        /// </summary>
        /// <remarks>BoundLookup control may populate other columns with the values returned by lookup page.</remarks>
        public virtual int[] AdditionalDisplayColumns
        {
            get { return this.additionalDisplayColumns; }
            set { this.additionalDisplayColumns = value; }
        }

        /// <summary>
        /// Get or sets the optional popup URL query key by which a unique client-side control ID 
        /// (see <see cref="Control.ClientID"/>) is sent to search page.
        /// </summary>
        /// <remarks>Tipically required to properly render <see cref="JsCommand"/> client script code.</remarks>
        public virtual string ClientIdQueryKey
        {
            get { return this.clientIdQueryKey; }
            set { this.clientIdQueryKey = value; }
        }

        /// <summary>
        /// Gets or sets the value indicating whether all generated scripts are to be prefixed with a unique 
        /// client-side control ID (see <see cref="Control.ClientID"/>).
        /// </summary>
        public virtual bool PrefixAllScriptsWithClientId
        {
            get { return this.prefixAllScriptsWithClientId; }
            set { this.prefixAllScriptsWithClientId = value; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the popup page is automatically openned 
        /// after the text of the generated <see cref="BoundLookup"/> controls is modified.
        /// </summary>
        public virtual bool AutoPopupOnChange
        {
            get { return this.autoPopupOnChange; }
            set { this.autoPopupOnChange = value; }
        }

        /// <summary>
        /// Gets or sets the URL of the clear button image.
        /// </summary>
        public virtual string ClearImageUrl
        {
            get { return this.clearImageUrl; }
            set { this.clearImageUrl = value; }
        }

        /// <summary>
        /// Gets or sets the URL of the lookup button image.
        /// </summary>
        public virtual string LookupImageUrl
        {
            get { return this.lookupImageUrl; }
            set { this.lookupImageUrl = value; }
        }

        /// <summary>
        /// Gets or sets the height of the popup window/iframe.
        /// </summary>
        public virtual Unit PopupHeight
        {
            get { return this.popupHeight; }
            set { this.popupHeight = value; }
        }

        /// <summary>
        /// Gets or sets the object position in pixels relative to the left edge of the document.
        /// </summary>
        public virtual int PopupLeftPx
        {
            get { return this.popupLeftPx; }
            set { this.popupLeftPx = value; }
        }

        /// <summary>
        /// Gets or sets the object position in pixels relative to the top edge of the document.
        /// </summary>
        public virtual int PopupTopPx
        {
            get { return this.popupTopPx; }
            set { this.popupTopPx = value; }
        }

        /// <summary>
        /// Gets or sets the width of the popup window/iframe.
        /// </summary>
        public virtual Unit PopupWidth
        {
            get { return this.popupWidth; }
            set { this.popupWidth = value; }
        }

        /// <summary>
        /// Gets or sets the optional popup URL query key by which the <b>Text</b> property 
        /// value of the generated <see cref="BoundLookup"/> controls is passed to popup page.
        /// </summary>
        public virtual string TextQueryKey
        {
            get { return this.textQueryKey; }
            set { this.textQueryKey = value; }
        }

        /// <summary>
        /// Gets or sets the text displayed when the mouse pointer hovers 
        /// over the the generated <see cref="BoundLookup"/> controls.
        /// </summary>
        public virtual string ToolTip
        {
            get { return this.toolTip; }
            set { this.toolTip = value; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether users can manually change 
        /// the contents of the generated <see cref="BoundLookup"/> controls. 
        /// </summary>
        /// <remarks>Default is <b>false</b>.</remarks>
        public virtual bool ReadOnly
        {
            get { return this.readOnly; }
            set { this.readOnly = value; }
        }

        /// <summary>
        /// Gets or sets the number of satellite/display fields which are displayed in the <b>Text</b> property of the generated <b>BoundLookup</b> controls.
        /// </summary>
        /// <value>The number of display fields displayed in the Text property. Default is <b>1</b>. Zero and negative values are converted to <b>1</b>.</value>
        public virtual int DisplayFieldsCount
        {
            get { return this.displayFieldsCount; }
            set { this.displayFieldsCount = (value < 1) ? 1 : value; }
        }

        /// <summary>
        /// Gets or sets the string which delimites the values of satellite/display fields displayed in the <b>Text</b> property of the generated <b>BoundLookup</b> controls.
        /// </summary>
        /// <value>A string. Default is one space <b>" "</b>. Nulls are converted to empty strings.</value>
        /// <seealso cref="DisplayFieldsCount"/>
        public virtual string DisplayFieldsDelimiter
        {
            get { return this.displayFieldsDelimiter; }
            set { this.displayFieldsDelimiter = value ?? ""; }
        }

        #endregion

        #region Instantiate and bind.

        /// <summary>
        /// Defines the <see cref="DataGrid"/> that child controls and the template belong to.
        /// </summary>
        /// <param name="container">The <b>TableCell</b> which contains the <b>BoundLookup</b> controls instantiated by this template.</param>
        public override void InstantiateIn(Control container)
        {
            BoundLookup lookup = new BoundLookup();
            lookup.DataBinding += new EventHandler(this.BindDisplayValue);
            if (this.SkinID != null)
            {
                lookup.SkinID = this.SkinID;
                Page page = HttpContext.Current.Handler as Page;
                if (page != null)
                    lookup.ApplyStyleSheetSkin(page);
            }

            container.Controls.Add(lookup);
        }

        private void BindDisplayValue(object sender, EventArgs e)
        {
            string displayExpression = this.BoundField.BindingName;
            if (this.BoundField.SatelliteFields != null && this.BoundField.SatelliteFields.Length > 0)
                displayExpression = this.BoundField.SatelliteFields[0];

            BoundLookup lookup = (BoundLookup)sender;
            lookup.PopupUrl = this.popupUrl;
            lookup.HideLookupJScript = HideLookupJScriptName;
            lookup.DisplayValuesJScript = SetValuesJScriptName;
            ApplyStyle(lookup);

            lookup.AutoPopupOnChange = this.autoPopupOnChange;
            lookup.ReadOnly = this.readOnly;
            if (this.clearImageUrl != null)
                lookup.ClearImageUrl = this.clearImageUrl;
            if (this.lookupImageUrl != null)
                lookup.LookupImageUrl = this.lookupImageUrl;
            if (this.textQueryKey != null)
                lookup.TextQueryKey = this.textQueryKey;
            if (this.toolTip != null)
                lookup.ToolTip = this.toolTip;
            if (this.popupHeight != default(Unit))
                lookup.PopupHeight = this.popupHeight;
            if (this.popupHeight != default(Unit))
                lookup.PopupWidth = this.popupWidth;
            if (this.popupLeftPx > 0)
                lookup.PopupLeftPx = this.popupLeftPx;
            if (this.popupTopPx > 0)
                lookup.PopupTopPx = this.popupTopPx;
            if (this.DisplayFieldsCount > 0)
                lookup.DisplayFieldsCount = this.DisplayFieldsCount;
            if (this.DisplayFieldsDelimiter != null)
                lookup.DisplayFieldsDelimiter = this.DisplayFieldsDelimiter;

            DataGridItem currentItem = (DataGridItem)lookup.NamingContainer;
            object keyValue = DataBinder.Eval(currentItem.DataItem, this.BoundField.BindingName);
            object displayValue = DataBinder.Eval(currentItem.DataItem, displayExpression);
            lookup.SelectedValue = this.Formatter.Format(keyValue);
            lookup.Text = this.Formatter.Format(displayValue);

            // Client must ensure that compatibile satellite controls exist in the row.
            if (this.additionalDisplayColumns != null)
            {
                DataGridItem row = (DataGridItem)lookup.Parent.Parent;
                foreach (int colIdx in this.additionalDisplayColumns)
                {
                    Control satelliteControl = row.Cells[colIdx].Controls[0];
                    lookup.AdditionalDisplayControls += satelliteControl.ClientID + ";";
                }
            }

            if (!string.IsNullOrEmpty(lookup.PopupUrl) && !string.IsNullOrEmpty(this.clientIdQueryKey))
            {
                string queryKeyDelimiter = lookup.PopupUrl.Contains("?") ? "&" : "?";
                lookup.PopupUrl += queryKeyDelimiter + this.clientIdQueryKey + "=" + lookup.ClientID;
            }

            if (this.prefixAllScriptsWithClientId)
            {
                lookup.HideLookupJScript = lookup.ClientID + lookup.HideLookupJScript;
                lookup.DisplayValuesJScript = lookup.ClientID + lookup.DisplayValuesJScript;
            }

            bool isReadOnly = ConfigureAccessMode(lookup);
            if (isReadOnly)
            {
                lookup.BackColor = Color.Transparent;
                lookup.Style["cursor"] = "pointer";
            }
        }

        #endregion

        #region ISearchPageClient.

        /// <summary>
        /// Creates a command which accepts the selected search/lookup values and sets them into the client component.
        /// </summary>
        /// <param name="calledFromPopupWindow">Indicates whether the provider is rendered in a popup/iframe window.</param>
        /// <param name="text">Command text.</param>
        /// <param name="requiredParameters">Parameters (ie. data fields) required for the accept command.</param>
        /// <returns><b>IWebCommand</b> object or <b>null</b> is the client doesn't support the accept command.</returns>
        public IWebCommand CreateAcceptCommand(bool calledFromPopupWindow, string text, string[] requiredParameters)
        {
            if (requiredParameters == null || requiredParameters.Length == 0)
                throw new ArgumentException("At least one parameter must be provided.", "requiredParameters");

            StringBuilder callFormat = new StringBuilder();
            if (calledFromPopupWindow)
                callFormat.Append("parent.");
            callFormat.Append(this.SetValuesJScriptName);
            callFormat.Append("(");
            for (int idxParameter = 0; idxParameter < requiredParameters.Length; idxParameter++)
            {
                callFormat.Append("'{");
                callFormat.Append(idxParameter);
                callFormat.Append("}'");
                if (idxParameter < requiredParameters.Length - 1)
                    callFormat.Append(", ");
            }
            callFormat.Append(")");

            JsCommand setValues = new JsCommand(null, text, callFormat.ToString(), requiredParameters);
            if (this.prefixAllScriptsWithClientId)
                setValues.RequiredContextVariables = new string[] { JsCommand.ContextVariable_ParentReplacementString };

            return setValues;
        }

        /// <summary>
        /// Creates a command which cancels the search/lookup action.
        /// </summary>
        /// <param name="calledFromPopupWindow">Indicates whether the provider is rendered in a popup/iframe window.</param>
        /// <param name="text">Command text.</param>
        /// <returns><b>IWebCommand</b> object.</returns>
        public IWebCommand CreateCancelCommand(bool calledFromPopupWindow, string text)
        {
            StringBuilder functionCall = new StringBuilder();
            if (calledFromPopupWindow)
                functionCall.Append("parent.");
            functionCall.Append(this.HideLookupJScriptName);
            functionCall.Append("()");

            JsCommand cancel = new JsCommand(null, text, functionCall.ToString());
            if (this.prefixAllScriptsWithClientId)
                cancel.RequiredContextVariables = new string[] { JsCommand.ContextVariable_ParentReplacementString };

            return cancel;
        }

        /// <summary>
        /// Has no meaning. The provider key must be specified via <see cref="ISearchPageClient.SearchPageUrl"/> property.
        /// </summary>
        string ISearchClient.SearchProviderKey
        {
            get { return null; }
            set { }
        }

        /// <summary>
        /// Gets or sets the URL of a page which uses/renders the registered <see cref="SearchProvider"/>.
        /// </summary>
        /// <remarks>URL must contain a query string value which specifies the provider key.</remarks>
        string ISearchPageClient.SearchPageUrl
        {
            get { return this.PopupUrl; }
            set { this.PopupUrl = value; }
        }

        #endregion
    }
}